Skip to content

Ralph/group conversations full feature#231

Closed
CayoPOliveira wants to merge 93 commits intoCayo-Oliveira/CU-86af00yvg/2-Backend-Models-Main-PRfrom
ralph/group-conversations-full-feature
Closed

Ralph/group conversations full feature#231
CayoPOliveira wants to merge 93 commits intoCayo-Oliveira/CU-86af00yvg/2-Backend-Models-Main-PRfrom
ralph/group-conversations-full-feature

Conversation

@CayoPOliveira
Copy link

@CayoPOliveira CayoPOliveira commented Feb 28, 2026

Pull Request Template

Description

Please include a summary of the change and issue(s) fixed. Also, mention relevant motivation, context, and any dependencies that this change requires.
Fixes # (issue)

Type of change

Please delete options that are not relevant.

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality not to work as expected)
  • This change requires a documentation update

How Has This Been Tested?

Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration.

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my code
  • I have commented on my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published in downstream modules

This change is Reviewable

Summary by CodeRabbit

Release Notes

  • New Features
    • Create and manage WhatsApp groups with custom settings (name, description, avatar)
    • Add, remove, or promote/demote group members
    • Generate, manage, and revoke group invite links
    • Approve or reject pending member join requests
    • Enhanced group messaging with member names and avatars
    • @Mention group members directly in conversations
    • Filter conversations by type (individual or group)

gabrieljablonski and others added 30 commits February 27, 2026 13:47
…ions

- Added PRD for group conversations detailing frontend and backend requirements.
- Created new Baileys TypeScript definitions for group-related functions.
- Renamed `conversation_type` to `group_type` in the database and updated all references.
- Implemented API serialization for `group_type` in conversation and contact responses.
- Developed Vuex store module for managing group members.
- Created UI components for group management, including group creation, member management, and metadata editing.
- Integrated @mention functionality for group conversations and real-time updates via ActionCable.
- Add migration to rename column and indexes
- Update Conversation model enum to group_type
- Update GroupConversationHandler concern
- Update controllers (contacts, group_members)
- Update all backend specs
- Add POST /api/v1/accounts/:account_id/groups endpoint
- Add Groups::CreateService to orchestrate Baileys group creation
- Extend WhatsappBaileysService and BaseService with group management methods
- Add routes for group members, metadata, invite, and join requests
- Returns 403 when agent lacks inbox access, 422 when provider is unavailable
- Add create/destroy/update actions to GroupMembersController
- Delegate group management methods from Channel::Whatsapp to provider_service
- create adds members via Baileys and creates ConversationGroupMember records
- destroy removes a member by ID and sets is_active false
- update promotes/demotes a member and updates their role
- Add PATCH /contacts/:id/group_metadata endpoint
- Updates group subject via Baileys and syncs contact name
- Updates group description via Baileys and syncs additional_attributes.description
- Returns 422 when provider is unavailable
- Add GET /contacts/:id/group_invite to retrieve current invite code/url
- Add POST /contacts/:id/group_invite/revoke to revoke and get new invite code/url
- Returns 422 when provider is unavailable
- Add GET /contacts/:id/group_join_requests to list pending join requests
- Add POST /contacts/:id/group_join_requests/handle to approve/reject requests
- Uses request_action param to avoid conflict with Rails reserved params[:action]
- Returns 422 when provider is unavailable
- Extract mention://contact/ID/Name URIs from message content
- Store mentioned contact IDs in message.content_attributes[mentioned_contacts]
- Existing user/team mention handling unchanged
- Add app/javascript/dashboard/api/groupMembers.js
- Exports 11 methods: getGroupMembers, syncGroup, createGroup, updateGroupMetadata,
  addMembers, removeMembers, updateMemberRole, getInviteLink, revokeInviteLink,
  getPendingRequests, handleJoinRequest
- Add groupMembers store module with fetch, sync, addMembers, removeMembers, updateMemberRole actions
- Add SET_GROUP_MEMBERS and SET_GROUP_MEMBERS_UI_FLAG mutation types
- Register module in store index
- Add groups.json with keys for group info, filter, creation modal, metadata editing, invite link, member management, join requests, and mention dropdown
- Register groups.json in i18n locale en/index.js

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add chatGroupTypeFilter state, getter, mutation, and action to conversations store
- Add getChatGroupTypeFilter getter
- Add group_type param to ConversationApi.get()
- Add Type filter section to ConversationBasicFilter with All/Individual/Group options
- Persist group_type to UI settings under conversations_filter_by.group_type
- Restore group_type from UI settings on page load
- Include groupType in conversationFilters and pass as group_type param to API

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…cFilter

All implementation was already in place from prior work:
- ConversationBasicFilter.vue has Type section with All/Individual/Group options
- ChatList.vue handles group_type in conversationFilters and restores from UI settings
- Store has setChatGroupTypeFilter action, getChatGroupTypeFilter getter
- API maps groupType → group_type query param

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add GROUP_TYPE to CONVERSATION_ATTRIBUTES in filterHelper.js
- Add group_type filter definition in provider.js (components-next)
- Add group_type to legacy advancedFilterItems/index.js and filterAttributeGroups
- Add group_type to automationHelper conditionFilterMaps
- Add group_type to customViewsHelper getValuesForFilter
- Add group_type options to ChatList setParamsForEditFolderModal
- Add GROUP_TYPE i18n key in en and pt_BR advancedFilters.json

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Import GroupContactInfo component
- Conditionally render GroupContactInfo when group_type === 'group'
- Keep ContactInfo for individual conversations (no regression)
- Dynamic sidebar title: 'Group' for groups, 'Contact' for individual
- contact_notes and contact_attributes accordion sections unchanged

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add inline editing for group name, description, and avatar in GroupContactInfo:
- Click group name to edit inline, save on Enter/blur
- Click description to edit inline with textarea, save on blur
- Click avatar to open file picker for upload via contacts/update
- Loading states on all fields during save
- Success/error alerts for all operations
- updateGroupMetadata action added to groupMembers store

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add sender name display above incoming message bubbles in group conversations
- Deterministic color per sender using AVATAR_COLORS palette (name.length % 6)
- Sender name hidden for consecutive messages from the same sender
- Individual conversation bubbles unchanged
- Pass groupWithPrevious and isGroupConversation props through MessageList → Message

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Everyone item shown at top of mention dropdown
- Searchable by 'all', 'todos', 'everyone' keywords
- i18n keys added for en and pt-BR
- Add disabled prop to Switch component
- Replace custom toggle buttons in GroupContactInfo with Switch
- Loading spinner shown alongside toggle while toggling
…roup

- Remove dead conversation/{id}/sync_group swagger entry and file
- Update group_members.yml with pagination params, POST operation, and $ref schema
- Add swagger for: group_members_member (PATCH/DELETE), group_metadata,
  group_invite, group_invite_revoke, group_join_requests,
  group_join_requests_handle, group_settings, group_settings_leave,
  group_settings_toggle_join_approval, groups/create
- Add group_member schema definition
- Add Groups tag to application tag_groups
- Register all 12 group endpoints in paths/index.yml
- Create Contacts::SyncGroupJob that checks group_last_synced_at
  before calling SyncGroupService (skips if < 15 min)
- Controller sync_group now enqueues the job and returns 202 Accepted
- Delete sync_group.json.jbuilder (no longer needed)
- Frontend sync action is fire-and-forget; results via ActionCable
- Auto-trigger sync on conversation select and panel mount
- Remove manual sync button from GroupContactInfo
…bers section visible in read-only mode when\ngroup_left is true. Admin actions (add member, promote,\ndemote, remove) remain hidden. Pending Join Requests and\nAdvanced Options also stay hidden.
…n\n- Remove the @all/everyone special mention from TagGroupMembers since\n no channel provider currently supports mentioning all participants\n- Fix Enter key sending message instead of inserting selected mention\n in group conversations. The root cause was Editor.vue only emitting\n toggleUserMention=true for private notes (isPrivate), leaving\n ReplyBox unaware the group mention dropdown was open. Now also\n emits for isGroupConversation.\n- Add TagGroupMembers spec covering filtering, exclusion, and emission"
@CayoPOliveira CayoPOliveira reopened this Mar 5, 2026
@CayoPOliveira
Copy link
Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Mar 5, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@CayoPOliveira
Copy link
Author

@codex review

@chatgpt-codex-connector
Copy link

To use Codex here, create a Codex account and connect to github.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds end-to-end WhatsApp group conversation functionality (backend models/APIs, sync, and dashboard UI), including member management, group metadata/invite/join-requests, group-specific rendering, and new filtering by conversation group type.

Changes:

  • Refactors group membership persistence from ConversationGroupMember to GroupMember (anchored on the group Contact) and renames conversation_typegroup_type on conversations.
  • Adds/updates APIs + Swagger docs for group creation and group operations (members, metadata, invites, join requests, settings).
  • Adds dashboard UX for group sidebar, group message bubbles, @mentions, and group-type filtering.

Reviewed changes

Copilot reviewed 143 out of 144 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
tasks/manual-testing.md Adds manual test checklist for group conversations
swagger/tag_groups/platform_swagger.json Adds group_member schema to platform swagger tag group
swagger/tag_groups/other_swagger.json Adds group_member schema to other swagger tag group
swagger/tag_groups/client_swagger.json Adds group_member schema to client swagger tag group
swagger/tag_groups/application_swagger.json Removes old endpoints and adds group_member schema in app swagger tag group
swagger/tag_groups/application.yml Adds Groups tag and fixes formatting
swagger/paths/index.yml Adds new group-related paths and groups create path; removes conversation sync_group path
swagger/paths/application/groups/create.yml Documents group creation endpoint
swagger/paths/application/conversation/sync_group.yml Removes deprecated conversation sync_group swagger path
swagger/paths/application/contacts/group_settings_toggle_join_approval.yml Documents join approval toggle endpoint
swagger/paths/application/contacts/group_settings_leave.yml Documents leave group endpoint
swagger/paths/application/contacts/group_settings.yml Documents group settings update endpoint
swagger/paths/application/contacts/group_metadata.yml Documents group metadata update endpoint
swagger/paths/application/contacts/group_members_member.yml Documents per-member update/remove endpoints
swagger/paths/application/contacts/group_members.yml Updates group members list + add members; adds pagination/meta
swagger/paths/application/contacts/group_join_requests_handle.yml Documents join request handling endpoint
swagger/paths/application/contacts/group_join_requests.yml Documents join request listing endpoint
swagger/paths/application/contacts/group_invite_revoke.yml Documents invite revoke endpoint
swagger/paths/application/contacts/group_invite.yml Documents invite show endpoint
swagger/definitions/resource/group_member.yml Adds reusable group_member schema
swagger/definitions/index.yml Registers group_member schema
spec/services/whatsapp/providers/whatsapp_baileys_service_spec.rb Updates provider response expectations and adds provider group API specs
spec/services/whatsapp/baileys_handlers/messages_upsert_spec.rb Updates specs for group_type and GroupMember
spec/services/whatsapp/baileys_handlers/groups_update_spec.rb Updates specs to use group_type
spec/services/whatsapp/baileys_handlers/group_participants_update_spec.rb Updates specs to use GroupMember and group_type
spec/services/messages/mention_service_spec.rb Adds specs for contact mentions storage behavior
spec/services/contacts/sync_group_service_spec.rb Updates sync behavior to use channel-based sync and conversation creation
spec/models/conversation_spec.rb Removes old sync/group member association specs; adds group_type enum spec
spec/models/conversation_group_member_spec.rb Removes specs for removed model
spec/models/contact_spec.rb Removes old conversation_group_member association specs
spec/jobs/contacts/sync_group_job_spec.rb Adds specs for new SyncGroupJob cooldown/rescue behavior
spec/factories/group_members.rb Adds factory for GroupMember
spec/factories/conversation_group_members.rb Removes old factory
spec/controllers/api/v1/accounts/groups_controller_spec.rb Adds request specs for group creation endpoint
spec/controllers/api/v1/accounts/conversations_controller_spec.rb Removes request specs for removed conversation sync_group endpoint
spec/controllers/api/v1/accounts/contacts_controller_spec.rb Updates sync_group to enqueue job and return 202
spec/controllers/api/v1/accounts/contacts/group_metadata_controller_spec.rb Adds request specs for group metadata update endpoint
spec/controllers/api/v1/accounts/contacts/group_join_requests_controller_spec.rb Adds request specs for join requests endpoints
spec/controllers/api/v1/accounts/contacts/group_invite_controller_spec.rb Adds request specs for invite endpoints
ralph.sh Adds long-running agent loop script (Ralph)
lib/filters/filter_keys.yml Adds group_type as a standard conversation filter key
enterprise/app/models/company.rb Updates schema comment formatting
db/migrate/20260303120000_drop_conversation_group_members.rb Drops old conversation_group_members table
db/migrate/20260303114847_create_group_members.rb Adds group_members table
db/migrate/20260227135739_rename_conversation_type_to_group_type_on_conversations.rb Renames conversations column/indexes to group_type
config/routes.rb Removes conversation sync_group route; adds groups + contact group subresources
app/views/api/v1/models/_inbox.json.jbuilder Exposes allow_group_creation on inbox payload
app/views/api/v1/models/_contact.json.jbuilder Exposes group_type on contact payload
app/views/api/v1/conversations/partials/_conversation.json.jbuilder Exposes group_type on conversation payload
app/views/api/v1/accounts/contacts/sync_group.json.jbuilder Removes old sync_group response body
app/views/api/v1/accounts/contacts/group_members/index.json.jbuilder Updates member payload and adds pagination meta
app/services/whatsapp/providers/base_service.rb Adds abstract group operations to provider base
app/services/whatsapp/mention_converter_service.rb Adds WhatsApp mention extraction/conversion utilities
app/services/whatsapp/baileys_handlers/messages_upsert.rb Adds group-create stub handling
app/services/whatsapp/baileys_handlers/helpers.rb Converts incoming mentions on text messages
app/services/whatsapp/baileys_handlers/groups_update.rb Persists invite/settings changes from group updates
app/services/whatsapp/baileys_handlers/group_participants_update.rb Updates group member tracking and resolves conversations when inbox leaves
app/services/whatsapp/baileys_handlers/concerns/group_stub_message_handler.rb Handles GROUP_CREATE and icon updates (sync + avatar refresh)
app/services/whatsapp/baileys_handlers/concerns/group_contact_message_handler.rb Updates member sync to use GroupMember
app/services/messages/mention_service.rb Adds parsing/storing of contact mentions
app/services/messages/markdown_renderers/whats_app_renderer.rb Avoids rendering mention links as raw URLs
app/services/groups/create_service.rb Adds service to create WhatsApp groups via provider
app/services/filter_service.rb Adds group_type to filter value coercion
app/services/contacts/sync_group_service.rb Refactors sync to use channel + ensured conversation creation
app/presenters/conversations/event_data_presenter.rb Adds group_type to conversation event payload
app/policies/conversation_policy.rb Removes sync_group authorization method
app/models/user.rb Updates schema comment formatting
app/models/super_admin.rb Updates schema comment formatting
app/models/reporting_event.rb Updates schema comment formatting
app/models/group_member.rb Adds new GroupMember model
app/models/csat_survey_response.rb Updates schema comment formatting
app/models/conversation_group_member.rb Removes old ConversationGroupMember model
app/models/conversation.rb Renames enum to group_type and removes group member associations/sync method
app/models/contact.rb Adds group membership associations and group_channel helper
app/models/concerns/group_conversation_handler.rb Updates group conversation/member management to use GroupMember
app/models/channel/whatsapp.rb Adds group-related delegation + allow_group_creation
app/listeners/action_cable_listener.rb Broadcasts group members on contact.group_synced events
app/jobs/contacts/sync_group_job.rb Adds background job for group sync with cooldown
app/javascript/shared/helpers/markdownIt/link.js Adds mention://contact support in markdown-it plugin
app/javascript/shared/constants/busEvents.js Adds NAVIGATE_TO_GROUP bus event
app/javascript/dashboard/store/mutation-types.js Adds group member and group-type filter mutations
app/javascript/dashboard/store/modules/groupMembers.spec.js Adds unit tests for groupMembers Vuex module
app/javascript/dashboard/store/modules/groupMembers.js Adds Vuex module for group member operations and group creation
app/javascript/dashboard/store/modules/conversations/index.js Adds group-type filter state/mutation
app/javascript/dashboard/store/modules/conversations/helpers/filterHelpers.js Adds group_type to frontend filtering helpers
app/javascript/dashboard/store/modules/conversations/getters.js Adds getter for group-type filter
app/javascript/dashboard/store/modules/conversations/actions.js Adds action to set group-type filter
app/javascript/dashboard/store/index.js Registers groupMembers Vuex module
app/javascript/dashboard/routes/dashboard/conversation/ContactPanel.vue Switches sidebar to GroupContactInfo for group chats and triggers sync
app/javascript/dashboard/i18n/locale/pt_BR/index.js Registers new groups locale bundle
app/javascript/dashboard/i18n/locale/pt_BR/groups.json Adds pt-BR group UI strings
app/javascript/dashboard/i18n/locale/pt_BR/contact.json Adds pt-BR compose tabs for group mode
app/javascript/dashboard/i18n/locale/pt_BR/advancedFilters.json Adds pt-BR label for group_type advanced filter
app/javascript/dashboard/i18n/locale/en/index.js Registers new groups locale bundle
app/javascript/dashboard/i18n/locale/en/groups.json Adds en group UI strings
app/javascript/dashboard/i18n/locale/en/contact.json Adds en compose tabs for group mode
app/javascript/dashboard/i18n/locale/en/advancedFilters.json Adds en label for group_type advanced filter
app/javascript/dashboard/helper/specs/actionCable.spec.js Adds tests for contact.group_synced handler
app/javascript/dashboard/helper/pendingGroupNavigation.js Adds helper for post-create navigation handshake
app/javascript/dashboard/helper/customViewsHelper.js Adds support for group_type values in custom views
app/javascript/dashboard/helper/automationHelper.js Adds group_type condition options in automations
app/javascript/dashboard/helper/actionCable.js Handles contact.group_synced + group navigation on conversation.created
app/javascript/dashboard/components/widgets/conversation/specs/TagGroupMembers.spec.js Adds tests for group member mention dropdown
app/javascript/dashboard/components/widgets/conversation/advancedFilterItems/index.js Adds group_type to advanced filter UI
app/javascript/dashboard/components/widgets/conversation/TagGroupMembers.vue Adds group member dropdown for @mentions
app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue Passes group context into editor for group mentions
app/javascript/dashboard/components/widgets/conversation/ConversationBasicFilter.vue Adds group type selection to basic filter dropdown
app/javascript/dashboard/components/widgets/WootWriter/Editor.vue Enables group-member mention UI in group conversations
app/javascript/dashboard/components/ChatList.vue Adds group_type filtering to conversation list fetching
app/javascript/dashboard/components-next/switch/Switch.vue Adds disabled prop support to switch component
app/javascript/dashboard/components-next/message/bubbles/Text/FormattedContent.vue Styles contact mentions distinctly
app/javascript/dashboard/components-next/message/MessageList.vue Passes group context for bubble grouping/name rendering
app/javascript/dashboard/components-next/message/Message.vue Renders sender avatar/name in group incoming messages + click-to-contact
app/javascript/dashboard/components-next/filter/provider.js Adds group_type to new filter provider
app/javascript/dashboard/components-next/filter/helper/filterHelper.js Adds GROUP_TYPE constant
app/javascript/dashboard/components-next/NewConversation/components/ComposeNewGroupForm.vue Adds UI to create a new WhatsApp group
app/javascript/dashboard/components-next/NewConversation/ComposeConversation.vue Adds “Conversation/Group” compose tabs and group creation flow
app/javascript/dashboard/api/inbox/conversation.js Sends group_type query param when fetching conversations
app/javascript/dashboard/api/groupMembers.js Adds API client for group endpoints
app/helpers/filters/filter_helper.rb Adds mapping for conversation group_type filter values
app/finders/conversation_finder.rb Adds backend filtering by group_type param
app/controllers/api/v1/accounts/groups_controller.rb Adds group creation endpoint
app/controllers/api/v1/accounts/conversations_controller.rb Removes conversation sync_group action
app/controllers/api/v1/accounts/contacts_controller.rb Changes sync_group to enqueue background job and return 202
app/controllers/api/v1/accounts/contacts/group_settings_controller.rb Adds group settings endpoints (leave/update/toggle join approval)
app/controllers/api/v1/accounts/contacts/group_metadata_controller.rb Adds group metadata update endpoint
app/controllers/api/v1/accounts/contacts/group_members_controller.rb Adds paginated member listing + add/remove/promote/demote
app/controllers/api/v1/accounts/contacts/group_join_requests_controller.rb Adds join request list/handle endpoints
app/controllers/api/v1/accounts/contacts/group_invites_controller.rb Adds invite show/revoke endpoints
AGENTS.md Adds Ralph agent instructions
.last-branch Stores last Ralph branch name
.claude/skills/ralph/SKILL.md Adds Ralph PRD converter skill docs
.claude/skills/prd/SKILL.md Adds PRD generator skill docs

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
app/models/csat_survey_response.rb (2)

36-40: ⚠️ Potential issue | 🟠 Major

Add model-level validation for message_id uniqueness/presence.

message_id has a UNIQUE DB index, but the model does not validate it. Add validates :message_id, presence: true, uniqueness: true (or scoped uniqueness if that is the domain rule) to fail fast at the model layer.

Proposed change
   validates :rating, presence: true, inclusion: { in: [1, 2, 3, 4, 5] }
   validates :account_id, presence: true
   validates :contact_id, presence: true
   validates :conversation_id, presence: true
+  validates :message_id, presence: true, uniqueness: true

As per coding guidelines, app/models/**/*.rb: "Always validate presence and uniqueness in Rails models, and add proper database indexes".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/models/csat_survey_response.rb` around lines 36 - 40, Add a model-level
validation for message_id on the CsatSurveyResponse model: ensure the model
(CsatSurveyResponse) validates presence and uniqueness of :message_id (or
uniqueness scoped to the relevant attribute if domain rules require, e.g.
uniqueness: { scope: :conversation_id }). Update the validates block to include
validates :message_id, presence: true, uniqueness: true (or scoped uniqueness)
so model-level checks mirror the UNIQUE DB index and fail fast before DB
constraint errors.

45-46: ⚠️ Potential issue | 🟠 Major

Back filter_by_rating with a database index.

A new rating filter scope was added, but schema annotations do not show an index on rating. This can cause table scans as data grows; add an index (often [:account_id, :rating] in multi-tenant queries).

As per coding guidelines, app/models/**/*.rb: "Always validate presence and uniqueness in Rails models, and add proper database indexes".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/models/csat_survey_response.rb` around lines 45 - 46, Add a DB index and
model validation for the new rating filter: create a Rails migration that adds
an index on [:account_id, :rating] for the csat_survey_responses table (e.g.,
add_index :csat_survey_responses, [:account_id, :rating]) to avoid table scans
in multi-tenant queries, and update the CsAtSurveyResponse model to validate
presence of :rating (e.g., validates :rating, presence: true) so the scope
filter_by_rating operates against an indexed, validated column.
app/javascript/dashboard/api/inbox/conversation.js (1)

19-33: ⚠️ Potential issue | 🟡 Minor

Add groupType parameter to ConversationApi.meta() to match backend support and frontend list filtering.

The backend conversation_finder filters by group_type for the meta endpoint, and get() already accepts groupType for filtered results. However, meta() lacks this parameter, causing meta counts (used in conversationStats) to desynchronize when conversations are filtered by group type. Update the method signature to accept groupType and pass it to the API.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/javascript/dashboard/api/inbox/conversation.js` around lines 19 - 33,
ConversationApi.meta() is missing the groupType parameter causing meta counts to
desync when filtering by group_type; update the meta method signature to accept
groupType and include it in the axios GET params (mirror how get() passes
groupType) so the backend receives group_type; specifically modify the
ConversationApi.meta function to add the groupType argument and add group_type:
groupType to the params object.
app/services/whatsapp/providers/whatsapp_baileys_service.rb (1)

773-788: ⚠️ Potential issue | 🟠 Major

Several new network methods bypass centralized error recovery.

Methods added in this PR (e.g., group create/update/invite/join-request operations) are not included in with_error_handling, so provider reconnection logic won’t run for those failures.

💡 Proposed fix
   with_error_handling :setup_channel_provider,
                       :disconnect_channel_provider,
                       :send_message,
                       :toggle_typing_status,
                       :update_presence,
                       :read_messages,
                       :unread_message,
                       :received_messages,
                       :group_metadata,
                       :sync_group,
                       :on_whatsapp,
                       :delete_message,
                       :edit_message,
+                      :create_group,
+                      :update_group_subject,
+                      :update_group_description,
+                      :update_group_participants,
+                      :group_invite_code,
+                      :revoke_group_invite,
+                      :group_join_requests,
+                      :handle_group_join_requests,
                       :group_leave,
                       :group_setting_update,
                       :group_join_approval_mode
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/services/whatsapp/providers/whatsapp_baileys_service.rb` around lines 773
- 788, The new group-related network methods (e.g., group_create, group_update,
group_invite, group_join_request — whatever exact method names were added in
this PR) are not wrapped by the existing with_error_handling call, so failures
there skip the provider reconnection/error recovery; update the
with_error_handling invocation to include each new group method name (add the
exact method identifiers you introduced) so they use the centralized error
handler (refer to the with_error_handling symbol to locate the call and add the
new method symbols to its argument list).
🟠 Major comments (15)
tasks/references/baileys.d.ts-61-61 (1)

61-61: ⚠️ Potential issue | 🟠 Major

Fix typo in API type names: BussinesBusiness

Lines 61 and 192 use Bussines (double 's'), but upstream @whiskeysockets/baileys documentation uses Business (single 's') for the correct spelling. This causes broken imports and incorrect API names.

Proposed fix
-    updateBussinesProfile: (args: import("../Types/Bussines.js").UpdateBussinesProfileProps) => Promise<any>;
+    updateBusinessProfile: (args: import("../Types/Business.js").UpdateBusinessProfileProps) => Promise<any>;
@@
-    addOrEditQuickReply: (quickReply: import("../Types/Bussines.js").QuickReplyAction) => Promise<void>;
+    addOrEditQuickReply: (quickReply: import("../Types/Business.js").QuickReplyAction) => Promise<void>;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tasks/references/baileys.d.ts` at line 61, The API type and import are
misspelled: change all occurrences of "Bussines" to "Business" so the type and
import match upstream; specifically update the declaration updateBussinesProfile
to use import("../Types/Business.js").UpdateBusinessProfileProps and rename the
type/export in the Types file (and any other references like
UpdateBussinesProfileProps or ../Types/Bussines.js) to
UpdateBusinessProfileProps / ../Types/Business.js to restore correct imports and
API names.
ralph.sh-51-53 (1)

51-53: ⚠️ Potential issue | 🟠 Major

Sanitize archive folder names derived from branch input.

FOLDER_NAME is built from branch text (sourced from prd.json via .last-branch file) and used in filesystem paths without sanitization. Branch strings containing / or other unsafe characters can create directory traversal vulnerabilities when passed to mkdir -p. For example, a branch name like ralph/../../../tmp would escape the intended archive directory.

Suggested fix
-    FOLDER_NAME=$(echo "$LAST_BRANCH" | sed 's|^ralph/||')
+    FOLDER_NAME=$(echo "$LAST_BRANCH" | sed 's|^ralph/||' | sed 's/[^A-Za-z0-9._-]/-/g')
+    FOLDER_NAME=${FOLDER_NAME:-unknown-branch}
     ARCHIVE_FOLDER="$ARCHIVE_DIR/$DATE-$FOLDER_NAME"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ralph.sh` around lines 51 - 53, FOLDER_NAME is taken directly from
LAST_BRANCH and used to build ARCHIVE_FOLDER, allowing path traversal or unsafe
characters; sanitize LAST_BRANCH before using it: strip any leading "ralph/"
prefix, remove any "../" or "/" segments, replace all characters except
[A-Za-z0-9._-] with a safe separator (e.g., "-"), ensure the resulting
FOLDER_NAME is non-empty and does not start with "-" (prepend a safe prefix if
needed), then use that sanitized FOLDER_NAME to construct ARCHIVE_FOLDER and
call mkdir -p; update references to the symbols LAST_BRANCH, FOLDER_NAME, and
ARCHIVE_FOLDER accordingly.
app/services/whatsapp/mention_converter_service.rb-107-113 (1)

107-113: ⚠️ Potential issue | 🟠 Major

Prevent partial-token mention replacements

Current regexes can replace substrings inside longer mentions (e.g., @ana inside @anabel), which can map mentions to the wrong contact.

Proposed fix
+    def mention_token_pattern(value, case_insensitive: false)
+      options = case_insensitive ? Regexp::IGNORECASE : nil
+      Regexp.new("@#{Regexp.escape(value)}(?=$|\\s|[[:punct:]])", options)
+    end
+
     def apply_lid_mention(text, lid, account, inbox) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
       contact = find_contact_by_lid(lid, account, inbox)
       return text unless contact

       contact_phone = contact.phone_number&.delete('+')
       display_name = contact.name.presence || contact_phone || lid
       encoded_name = ERB::Util.url_encode(display_name)
       mention_uri = "[@#{display_name}](mention://contact/#{contact.id}/#{encoded_name})"

       # Try `@lid` first, then `@phone`, then `@displayName`
-      patterns = [/@#{Regexp.escape(lid)}/]
-      patterns << /@#{Regexp.escape(contact_phone)}/ if contact_phone.present?
-      patterns << /@#{Regexp.escape(display_name)}/i if display_name != lid && display_name != contact_phone
+      patterns = [mention_token_pattern(lid)]
+      patterns << mention_token_pattern(contact_phone) if contact_phone.present?
+      patterns << mention_token_pattern(display_name, case_insensitive: true) if display_name != lid && display_name != contact_phone

       patterns.each do |pattern|
         return text.sub(pattern, mention_uri) if text.match?(pattern)
       end
@@
     def replace_mention_in_text(text, phone, display_name, mention_uri)
       # Try `@phone` first, then `@DisplayName`
       patterns = [
-        /@#{Regexp.escape(phone)}/,
-        /@#{Regexp.escape(display_name)}/i
+        mention_token_pattern(phone),
+        mention_token_pattern(display_name, case_insensitive: true)
       ]

       patterns.each do |pattern|
         return text.sub(pattern, mention_uri) if text.match?(pattern)
       end

Also applies to: 148-157

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/services/whatsapp/mention_converter_service.rb` around lines 107 - 113,
The regexes for building mention patterns allow partial matches (e.g., matching
`@ana` inside `@anabel`); update the patterns in the block that builds `patterns =
[/@#{Regexp.escape(lid)}/]` (and the similar block later at lines ~148-157) to
require that the mention token is not followed by a word character by appending
a negative lookahead, e.g. use /@#{Regexp.escape(lid)}(?!\w)/,
/@#{Regexp.escape(contact_phone)}(?!\w)/ and
/@#{Regexp.escape(display_name)}(?!\w)/i so the `patterns.each` loop and
`text.sub(pattern, mention_uri)` only replace whole mention tokens, not
substrings inside longer mentions.
swagger/paths/application/contacts/group_invite.yml-3-8 (1)

3-8: ⚠️ Potential issue | 🟠 Major

Change contact_id type from number to integer in group-related endpoints.

The number type in OpenAPI permits decimal values, which is inappropriate for identifiers. This issue affects multiple group-related endpoints: group_invite.yml, group_metadata.yml, group_invite_revoke.yml, group_join_requests.yml, group_join_requests_handle.yml, group_settings.yml, group_settings_leave.yml, and group_settings_toggle_join_approval.yml all use type: number for the contact_id path parameter, while request/response payloads correctly use type: integer (e.g., in merge.yml).

Suggested fix
   - name: contact_id
     in: path
     required: true
     schema:
-      type: number
+      type: integer
     description: ID of the group contact
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@swagger/paths/application/contacts/group_invite.yml` around lines 3 - 8, The
path parameter "contact_id" is currently defined with schema type: number in
several group-related endpoints; change each occurrence to schema type: integer
(and optionally add format: int64 if you want explicit sizing) so the path param
cannot accept decimals—update contact_id in the group-related YAMLs
(group_invite.yml, group_metadata.yml, group_invite_revoke.yml,
group_join_requests.yml, group_join_requests_handle.yml, group_settings.yml,
group_settings_leave.yml, group_settings_toggle_join_approval.yml) to use type:
integer to match request/response payloads.
swagger/paths/application/contacts/group_join_requests.yml-3-8 (1)

3-8: ⚠️ Potential issue | 🟠 Major

Use integer (not number) for contact_id path parameters.

Path parameters should consistently use integer for ID fields. Currently, type: number allows decimals, which is inappropriate for database IDs and can leak into generated SDK types and validation behavior. This applies to all 8 contact group endpoints:

  • group_join_requests.yml
  • group_join_requests_handle.yml
  • group_invite.yml
  • group_invite_revoke.yml
  • group_settings.yml
  • group_settings_leave.yml
  • group_settings_toggle_join_approval.yml
  • group_metadata.yml

Align with the account_id reference parameter in swagger/parameters/account_id.yml, which correctly uses type: integer.

Suggested fix
   - name: contact_id
     in: path
     required: true
     schema:
-      type: number
+      type: integer
     description: ID of the group contact

Apply this change to all 8 contact group endpoint files listed above.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@swagger/paths/application/contacts/group_join_requests.yml` around lines 3 -
8, The path parameter "contact_id" in the contacts group endpoints is declared
as type: number (allowing decimals); change it to type: integer for all listed
files (group_join_requests.yml, group_join_requests_handle.yml,
group_invite.yml, group_invite_revoke.yml, group_settings.yml,
group_settings_leave.yml, group_settings_toggle_join_approval.yml,
group_metadata.yml) so the contact_id path parameter uses type: integer
(matching the account_id parameter reference) to prevent decimal IDs and fix
generated SDK/validation behavior.
app/javascript/dashboard/helper/actionCable.js-93-96 (1)

93-96: ⚠️ Potential issue | 🟠 Major

Consume the pending JID only after a successful match.
Line 93 currently clears pending navigation before validating the sender identifier. A non-matching conversation.created event can discard the token and prevent later navigation to the intended group.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/javascript/dashboard/helper/actionCable.js` around lines 93 - 96, The
code currently calls pendingGroupNavigation.consume() before verifying the
sender, which can discard the token on a non-matching event; change the flow to
read the pending JID without consuming it (e.g., use a peek/get method on
pendingGroupNavigation instead of consume, or otherwise inspect the token) and
compare it to data.meta?.sender?.identifier first, and only call
pendingGroupNavigation.consume() after a successful match, then call
emitter.emit(BUS_EVENTS.NAVIGATE_TO_GROUP, { conversationId: data.id }); using
the same pendingJid for the check.
app/controllers/api/v1/accounts/groups_controller.rb-3-10 (1)

3-10: ⚠️ Potential issue | 🟠 Major

Use strong params before passing request data to the service.

Lines 3, 8–9 consume raw params directly, bypassing controller-layer type and shape validation. Add a group_params method and use it instead.

🔧 Suggested fix
 def create
-  inbox = Current.account.inboxes.find_by(id: params[:inbox_id])
+  inbox = Current.account.inboxes.find_by(id: group_params[:inbox_id])
   return render json: { error: 'Access Denied' }, status: :forbidden unless inbox_accessible?(inbox)

   result = Groups::CreateService.new(
     inbox: inbox,
-    subject: params[:subject],
-    participants: Array(params[:participants])
+    subject: group_params[:subject],
+    participants: Array(group_params[:participants])
   ).perform

   render json: result
 rescue Whatsapp::Providers::WhatsappBaileysService::ProviderUnavailableError => e
   render json: { error: e.message }, status: :unprocessable_entity
 end

 private
+
+def group_params
+  params.permit(:inbox_id, :subject, participants: [])
+end
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/controllers/api/v1/accounts/groups_controller.rb` around lines 3 - 10,
Request data is consumed directly from params when finding the inbox and calling
Groups::CreateService; add a strong params method and use it instead. Implement
a private group_params (e.g. params.permit(:inbox_id, :subject, participants: []
) or params.require(:group).permit(...)) and change inbox lookup to use
group_params[:inbox_id] and pass subject: group_params[:subject], participants:
Array(group_params[:participants]) into Groups::CreateService; keep using
inbox_accessible? and ensure group_params is used everywhere you read these
incoming values.
app/controllers/api/v1/accounts/contacts_controller.rb-87-88 (1)

87-88: ⚠️ Potential issue | 🟠 Major

Replace ActionController::BadRequest with a custom exception from lib/custom_exceptions/contacts.rb.

Lines 87–88 use ActionController::BadRequest, a framework exception. Per project guidelines (**/*.rb), validation errors must use custom exceptions from lib/custom_exceptions/. Create a new lib/custom_exceptions/contacts.rb module with appropriate exception classes (e.g., InvalidGroupType, MissingIdentifier) that inherit from CustomExceptions::Base and override http_status to return 400 for validation failures.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/controllers/api/v1/accounts/contacts_controller.rb` around lines 87 - 88,
Add a custom exceptions file and raise those instead of framework errors: create
lib/custom_exceptions/contacts.rb defining module CustomExceptions::Contacts
with classes InvalidGroupType and MissingIdentifier that inherit from
CustomExceptions::Base and override http_status to return 400; then update
app/controllers/api/v1/accounts/contacts_controller.rb to replace the two raises
(the checks using `@contact.group_type_individual`? and
`@contact.identifier.blank`?) to raise
CustomExceptions::Contacts::InvalidGroupType and
CustomExceptions::Contacts::MissingIdentifier respectively (ensure the new
module is loaded/autoloaded).
app/controllers/api/v1/accounts/contacts/group_join_requests_controller.rb-12-12 (1)

12-12: ⚠️ Potential issue | 🟠 Major

Use strong params for participants and request_action before forwarding to channel.

Raw params are passed directly into provider logic without whitelisting.

Suggested fix
 def handle
   authorize `@contact`, :update?
-  channel.handle_group_join_requests(`@contact.identifier`, params[:participants], params[:request_action])
+  channel.handle_group_join_requests(
+    `@contact.identifier`,
+    handle_params[:participants],
+    handle_params[:request_action]
+  )
   head :ok
 rescue Whatsapp::Providers::WhatsappBaileysService::ProviderUnavailableError => e
   render json: { error: e.message }, status: :unprocessable_entity
 end

 private

+def handle_params
+  params.permit(:request_action, participants: [])
+end

As per coding guidelines app/controllers/**/*.rb: Use strong params in Rails controllers for type safety and parameter validation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/controllers/api/v1/accounts/contacts/group_join_requests_controller.rb`
at line 12, The controller currently forwards raw params to
channel.handle_group_join_requests; fix by whitelisting and validating
participants and request_action via strong params before calling the channel.
Add a private method in GroupJoinRequestsController (e.g.,
group_join_request_params) that does
params.require(:some_parent_if_needed).permit(participants: [], :request_action)
and normalizes/validates types (ensure participants is an Array and
request_action is a permitted value), then call
channel.handle_group_join_requests(`@contact.identifier`,
group_join_request_params[:participants],
group_join_request_params[:request_action]).
app/services/whatsapp/baileys_handlers/groups_update.rb-84-90 (1)

84-90: ⚠️ Potential issue | 🟠 Major

Make invite-code removal logic consistent with description-update pattern.

The persist_invite_code_update method skips database updates when inviteCode is blank, which differs from how update_group_description handles blank values (line 48 uses .presence to store nil). For consistency and robustness, handle invite-code removal by storing nil or removing the key rather than skipping the update entirely.

Suggested fix
 def persist_invite_code_update(conversation, update)
   contact = conversation.contact
-  invite_code = update[:inviteCode]
-  return if invite_code.blank?
-
-  new_attrs = (contact.additional_attributes || {}).merge('invite_code' => invite_code)
+  new_attrs = (contact.additional_attributes || {}).dup
+  if update[:inviteCode].present?
+    new_attrs['invite_code'] = update[:inviteCode]
+  else
+    new_attrs.delete('invite_code')
+  end
   contact.update!(additional_attributes: new_attrs) if new_attrs != contact.additional_attributes
 end
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/services/whatsapp/baileys_handlers/groups_update.rb` around lines 84 -
90, persist_invite_code_update currently returns early when update[:inviteCode]
is blank; instead follow the update_group_description pattern by normalizing
invite_code = update[:inviteCode].presence (so blank becomes nil) and then build
new_attrs from (contact.additional_attributes || {}).merge('invite_code' =>
invite_code) or remove the 'invite_code' key when invite_code is nil, then call
contact.update! when new_attrs != contact.additional_attributes; reference
persist_invite_code_update, contact.additional_attributes and 'invite_code' when
making the change.
swagger/tag_groups/application_swagger.json-7942-7991 (1)

7942-7991: 🛠️ Refactor suggestion | 🟠 Major

Replace inline group_members.items definition with $ref to the new schema.

The new group_member schema (lines 7942–7991) is unused. Update line 2045 (items) to use { "$ref": "#/components/schemas/group_member" } instead of the current inline object definition. This eliminates duplication and ensures consistency between schema and endpoint documentation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@swagger/tag_groups/application_swagger.json` around lines 7942 - 7991, Update
the schema so the existing inline definition used for the group's members list
is replaced with a reference to the new reusable schema: locate the schema named
"group_members" (the property that has an "items" definition) and change its
items value to use { "$ref": "#/components/schemas/group_member" }; ensure the
referenced schema name is exactly "group_member" to match the newly added
components schema and remove the duplicated inline object.
app/services/whatsapp/providers/whatsapp_baileys_service.rb-6-6 (1)

6-6: 🛠️ Refactor suggestion | 🟠 Major

Use a shared custom exception class instead of defining a new local StandardError.

Please move this to lib/custom_exceptions/ and reference that class here for consistency with the project error model.

As per coding guidelines "Use custom exceptions from lib/custom_exceptions/ for error handling".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/services/whatsapp/providers/whatsapp_baileys_service.rb` at line 6, The
local exception class GroupParticipantNotAllowedError should be removed and
replaced with the shared exception from lib/custom_exceptions; delete the local
"class GroupParticipantNotAllowedError < StandardError" declaration, add the
appropriate require/import for the shared exception (e.g. require
'custom_exceptions/group_participant_not_allowed_error' or require
'custom_exceptions' depending on project convention), and update any references
in this file (whatsapp_baileys_service.rb) to use the shared
GroupParticipantNotAllowedError class instead of the local definition to align
with the project's error model.
app/services/whatsapp/providers/whatsapp_baileys_service.rb-558-567 (1)

558-567: ⚠️ Potential issue | 🟠 Major

Mention extraction is using the wrong source field.

This path sends @message.outgoing_content, but mention extraction/replacement reads @message.content. If content is empty, mention metadata won’t be applied.

💡 Proposed fix
   def merge_mention_data
-    return if `@message.content.blank`?
+    source_text = `@message.outgoing_content.presence` || `@message.content`
+    return if source_text.blank?
 
-    mention_data = Whatsapp::MentionConverterService.extract_mentions_for_whatsapp(`@message.content`, whatsapp_channel.account)
+    mention_data = Whatsapp::MentionConverterService.extract_mentions_for_whatsapp(source_text, whatsapp_channel.account)
     `@message_content.merge`!(mention_data) if mention_data.present?
 
     # Replace `@DisplayName` with `@lid/`@phone in text so Baileys can match mentions
     `@message_content`[:text] = Whatsapp::MentionConverterService.replace_mentions_in_outgoing_text(
-      `@message.content`, `@message_content`[:text], whatsapp_channel.account
+      source_text, `@message_content`[:text], whatsapp_channel.account
     )
   end
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/services/whatsapp/providers/whatsapp_baileys_service.rb` around lines 558
- 567, The code is reading the wrong source field for mentions: change the blank
check and both calls to use `@message.outgoing_content` instead of
`@message.content`; specifically, in merge_mention_data update the guard to return
if `@message.outgoing_content.blank`?, call
Whatsapp::MentionConverterService.extract_mentions_for_whatsapp(`@message.outgoing_content`,
whatsapp_channel.account) and pass `@message.outgoing_content` into
Whatsapp::MentionConverterService.replace_mentions_in_outgoing_text so mention
metadata and text replacements are derived from outgoing_content.
db/schema.rb-880-890 (1)

880-890: ⚠️ Potential issue | 🟠 Major

Add foreign keys for inbox_signatures references.

user_id and inbox_id are required but currently not FK-constrained in schema, which can leave orphan records.

💡 Migration follow-up
+ add_foreign_key :inbox_signatures, :users
+ add_foreign_key :inbox_signatures, :inboxes
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@db/schema.rb` around lines 880 - 890, Add foreign key constraints for
inbox_signatures.user_id and inbox_signatures.inbox_id to prevent orphan
records: create a migration that adds add_foreign_key :inbox_signatures, :users,
column: :user_id and add_foreign_key :inbox_signatures, :inboxes, column:
:inbox_id (or use change_table with t.foreign_key), ensure the migration
references the existing indexes index_inbox_signatures_on_inbox_id and
index_inbox_signatures_on_user_id_and_inbox_id and sets appropriate on_delete
behavior (e.g., :cascade or :nullify) consistent with your app rules, then run
migrations and update schema.rb accordingly.
app/javascript/dashboard/store/modules/groupMembers.js-47-49 (1)

47-49: ⚠️ Potential issue | 🟠 Major

Preserve original API errors instead of re-wrapping them.

throw new Error(error) strips useful fields from axios errors (like response.status / response.data). Re-throw the original error object.

💡 Proposed fix
-    } catch (error) {
-      throw new Error(error);
+    } catch (error) {
+      throw error;
     } finally {

Apply the same change to all four catch blocks in this file (lines 48, 72, 98, 119).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/javascript/dashboard/store/modules/groupMembers.js` around lines 47 - 49,
Replace the re-wrapping of caught errors with re-throwing the original error
object so Axios-specific fields are preserved: change any "throw new
Error(error)" in groupMembers.js to "throw error" (apply this to all catch
blocks in the file so response.status/response.data and other error properties
remain intact).
🟡 Minor comments (20)
app/javascript/dashboard/helper/automationHelper.js-154-157 (1)

154-157: ⚠️ Potential issue | 🟡 Minor

Avoid hard-coded English labels in group_type options.

Line 154-Line 157 hard-code display labels (Individual, Group), which can bypass i18n and show mixed-language UI in localized installs. Keep IDs static, but source names from translated options passed into this helper.

Proposed fix
 export const getConditionOptions = ({
   agents,
   booleanFilterOptions,
   campaigns,
   contacts,
   countries,
   customAttributes,
   inboxes,
   languages,
   labels,
   statusFilterOptions,
   teams,
   type,
   priorityOptions,
   messageTypeOptions,
+  groupTypeOptions,
 }) => {
@@
     message_type: messageTypeOptions,
     priority: priorityOptions,
-    group_type: [
-      { id: 'individual', name: 'Individual' },
-      { id: 'group', name: 'Group' },
-    ],
+    group_type: groupTypeOptions || [],
     labels: generateConditionOptions(labels, 'title'),
   };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/javascript/dashboard/helper/automationHelper.js` around lines 154 - 157,
The group_type array currently contains hard-coded English labels; change it to
keep static ids but pull the display names from the translations passed into
this helper (e.g., a translations/options param) instead of literal strings.
Replace the name values in the group_type entries with something like
translatedOptions.group_type.individual and translatedOptions.group_type.group
(or use i18n.t('...') if the helper receives an i18n instance), ensuring the
keys remain { id: 'individual' } and { id: 'group' } so callers provide
localized labels rather than embedding English text in group_type.
AGENTS.md-126-135 (1)

126-135: ⚠️ Potential issue | 🟡 Minor

Add language identifiers to fenced code blocks.

Two fenced blocks are missing language tags, which triggers MD040.

Suggested fix
-```
+```text
 ## [Date/Time] - [Story ID]
 - What was implemented
 - Files changed
 - **Learnings for future iterations:**
   - Patterns discovered (e.g., "this codebase uses X for Y")
   - Gotchas encountered (e.g., "don't forget to update Z when changing W")
   - Useful context (e.g., "the evaluation panel is in component X")
 ---

@@
- +text

Codebase Patterns

  • Example: Use sql<number> template for aggregations
  • Example: Always use IF NOT EXISTS for migrations
  • Example: Export types from actions.ts for UI components

Also applies to: 143-148

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@AGENTS.md` around lines 126 - 135, Two fenced code blocks (the one starting
with "## [Date/Time] - [Story ID]" and the one starting with "## Codebase
Patterns") are missing language identifiers which triggers MD040; update each
opening fence from ``` to a fenced block with a language tag (e.g., ```text) so
the Markdown linter recognizes them. Locate the blocks containing those headings
and replace their triple-backtick openers with language-tagged openers
(consistent tag like "text") for both occurrences mentioned.
app/services/whatsapp/baileys_handlers/concerns/group_stub_message_handler.rb-40-48 (1)

40-48: ⚠️ Potential issue | 🟡 Minor

Use presence for group name fallback.

If messageStubParameters.first is an empty string, group_name || group_jid keeps an empty name instead of falling back to the JID.

Suggested fix
-          name: group_name || group_jid,
+          name: group_name.presence || group_jid,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/services/whatsapp/baileys_handlers/concerns/group_stub_message_handler.rb`
around lines 40 - 48, The code uses group_name =
`@raw_message`[:messageStubParameters]&.first then sets name: group_name ||
group_jid which keeps an empty string instead of falling back; change to use
presence so empty string falls back (e.g. use group_name.presence || group_jid
or compute group_name = `@raw_message`[:messageStubParameters]&.first.presence)
and keep the rest of the ContactInboxWithContactBuilder call (group_jid, inbox,
group_contact_inbox) unchanged.
.claude/skills/prd/SKILL.md-34-52 (1)

34-52: ⚠️ Potential issue | 🟡 Minor

Specify a language on the fenced example block.

The example block should include a language identifier to satisfy markdown lint (MD040).

Suggested fix
-```
+```text
 1. What is the primary goal of this feature?
    A. Improve user onboarding experience
    B. Increase user retention
    C. Reduce support burden
    D. Other: [please specify]
@@
 3. What is the scope?
    A. Minimal viable version
    B. Full-featured implementation
    C. Just the backend/API
    D. Just the UI
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @.claude/skills/prd/SKILL.md around lines 34 - 52, The fenced code block
starting with "1. What is the primary goal of this feature?" is missing a
language identifier, which triggers MD040; update the opening triple-backtick to
include a language (for example "text") so the block becomes "text" and
keep the block contents unchanged; locate the fenced block by searching for the
lines that begin "1. What is the primary goal..." and the closing
triple-backticks and add the language token to the opening fence.


</details>

</blockquote></details>
<details>
<summary>tasks/prd-group-conversations-frontend.md-5-16 (1)</summary><blockquote>

`5-16`: _⚠️ Potential issue_ | _🟡 Minor_

**Align data-model references with current GroupMember architecture.**

This PRD still instructs `ConversationGroupMember`-based behavior and includes `conversation_id` in member payload examples, which is out of sync with the GroupMember/contact-level model adopted in this feature set. Please update those sections to avoid implementation drift.




Also applies to: 190-207, 424-425

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@tasks/prd-group-conversations-frontend.md` around lines 5 - 16, Update the
PRD to replace all references and examples that mention the deprecated
ConversationGroupMember join-table and any payloads containing conversation_id
with the current contact-level GroupMember model: use GroupMember (or
Contact-level group member) terminology, show payload examples that omit
conversation_id and instead reference contact_id/group_id as appropriate, and
adjust descriptions of member attributes (role, is_active) to reflect they are
stored on GroupMember/Contact not on a conversation join record; also update any
procedures that mention ConversationGroupMember, Conversation ↔ contact join
semantics, and the sync endpoint POST
/api/v1/accounts/:account_id/contacts/:id/sync_group to clarify it operates
against Contact/GroupMember metadata.
```

</details>

</blockquote></details>
<details>
<summary>.claude/skills/ralph/SKILL.md-100-112 (1)</summary><blockquote>

`100-112`: _⚠️ Potential issue_ | _🟡 Minor_

**Add language identifiers to fenced code blocks (MD040).**

Three fenced blocks are missing a language tag.

<details>
<summary>Suggested fix</summary>

````diff
-```
+```text
 "Typecheck passes"
 ```
@@
-```
+```text
 "Tests pass"
 ```
@@
-```
+```text
 "Verify in browser using dev-browser skill"
 ```
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.claude/skills/ralph/SKILL.md around lines 100 - 112, The three fenced code
blocks containing the literal lines "Typecheck passes", "Tests pass", and
"Verify in browser using dev-browser skill" are missing language identifiers;
update each opening fence to include a language (e.g., change ``` to ```text) so
the blocks become fenced with a language tag and satisfy MD040 for the blocks in
SKILL.md that contain those exact strings.
spec/services/contacts/sync_group_service_spec.rb-17-21 (1)

17-21: ⚠️ Potential issue | 🟡 Minor

Use class-name assertion for parallel-safe error checks.

Line 20 still asserts constant equality directly. Prefer asserting error.class.name to avoid reloading/parallel-class identity edge cases in specs.

Suggested adjustment
- expect { described_class.new(contact: contact).perform }.to raise_error(ActionController::BadRequest)
+ expect { described_class.new(contact: contact).perform }.to raise_error { |error|
+   expect(error.class.name).to eq('ActionController::BadRequest')
+ }

As per coding guidelines, "In parallel/reloading test environments, prefer comparing error.class.name over constant class equality when asserting raised errors".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@spec/services/contacts/sync_group_service_spec.rb` around lines 17 - 21, The
spec currently asserts raised error by comparing to the constant
ActionController::BadRequest which can fail under parallel/reload class
identity; change the assertion to inspect the raised error's class name instead:
wrap the call to described_class.new(contact: contact).perform in an expect {
... }.to raise_error block and assert error.class.name equals
"ActionController::BadRequest" (i.e., use error.class.name string comparison
rather than constant equality) so the test is parallel/reload safe.
app/javascript/dashboard/routes/dashboard/conversation/contact/GroupContactInfo.vue-66-74 (1)

66-74: ⚠️ Potential issue | 🟡 Minor

Phone matching heuristic may produce false positives.

The phonesMatch function compares only the last 8 digits, which could incorrectly match different phone numbers that happen to share the same suffix (e.g., different country codes with the same local number). Consider a stricter comparison or documenting the known limitations.

// Example of false positive:
// +1-555-123-4567 (US)
// +44-555-123-4567 (UK)
// Both would match because last 8 digits are "1234567" (or close)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/javascript/dashboard/routes/dashboard/conversation/contact/GroupContactInfo.vue`
around lines 66 - 74, phonesMatch currently compares only the last 8 digits
which can yield false positives; update the phonesMatch function to normalize
digits and then prefer exact equality of normalized numbers (a === b), and if
not equal only allow a suffix match when the original normalized lengths are the
same or when country/area code equality can be established (e.g., compare
leading length or leading digits), or alternatively tighten the suffix check to
more digits (e.g., last 10) and add a clear comment about the heuristic
limitation; reference the phonesMatch function and its local variables a and b
when making this change.
app/services/whatsapp/mention_converter_service.rb-75-77 (1)

75-77: ⚠️ Potential issue | 🟡 Minor

Guard against malformed mentionedJid entries

If mentionedJid contains nil/blank elements, jid.split('@') can crash this path. Add a defensive skip before split.

Proposed guard
       mentioned_jids.reduce(text) do |result, jid|
-        jid_user, jid_server = jid.split('@')
+        next result if jid.blank?
+        jid_user, jid_server = jid.to_s.split('@', 2)
+        next result if jid_user.blank?

         if jid_server == 'lid'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/services/whatsapp/mention_converter_service.rb` around lines 75 - 77, The
reduce over mentioned_jids can raise when a jid is nil/blank because
jid.split('@') is called unguarded; update the reducer in
MentionConverterService (the mentioned_jids.reduce block) to skip any nil/blank
or malformed jid entries before calling split (e.g., check jid.present? and that
it contains '@' or convert to string and guard), returning the current result
when skipping so the loop continues safely.
spec/services/whatsapp/providers/whatsapp_baileys_service_spec.rb-1570-1573 (1)

1570-1573: ⚠️ Potential issue | 🟡 Minor

Use error.class.name assertions for raised errors in these new specs

Switch these raise_error(SomeConstant) assertions to checking error.class.name to avoid constant reloading flakes in parallel/reloading environments.

Proposed spec update
      expect do
        service.update_group_participants(group_jid, [participant_jid], 'add')
-     end.to raise_error(Whatsapp::Providers::WhatsappBaileysService::ProviderUnavailableError)
+     end.to raise_error { |error|
+       expect(error.class.name).to eq('Whatsapp::Providers::WhatsappBaileysService::ProviderUnavailableError')
+     }

      expect do
        service.group_invite_code(group_jid)
-     end.to raise_error(Whatsapp::Providers::WhatsappBaileysService::ProviderUnavailableError)
+     end.to raise_error { |error|
+       expect(error.class.name).to eq('Whatsapp::Providers::WhatsappBaileysService::ProviderUnavailableError')
+     }

Applies to lines 1570-1573 and 1599-1602.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@spec/services/whatsapp/providers/whatsapp_baileys_service_spec.rb` around
lines 1570 - 1573, Replace direct raise_error(SomeConstant) expectations with
assertions that check the raised exception's class name string to avoid constant
reloading flakes; for the spec around calling
service.update_group_participants(group_jid, [participant_jid], 'add') (and the
similar block at lines ~1599-1602), wrap the call in an expect { ... }.to
raise_error, capture the rescued error (or use expect { ... }.to raise_error {
|err| ... }) and assert err.class.name ==
'Whatsapp::Providers::WhatsappBaileysService::ProviderUnavailableError' (use the
exact class name string used in the original assertion) instead of referring to
the constant directly.
swagger/paths/application/contacts/group_metadata.yml-15-30 (1)

15-30: ⚠️ Potential issue | 🟡 Minor

Schema does not enforce the “at least one field required” contract.

The description states one of subject or description must be present, but the request schema allows an empty object.

Schema fix
       application/json:
         schema:
           type: object
+          anyOf:
+            - required: [subject]
+            - required: [description]
           properties:
             subject:
               type: string
               description: New group subject (name)
             description:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@swagger/paths/application/contacts/group_metadata.yml` around lines 15 - 30,
The request schema currently allows an empty object but the description requires
at least one of subject or description; update the schema under the requestBody
→ content → application/json → schema to enforce that by adding a oneOf (or
anyOf) constraint that lists two alternatives: one requiring ["subject"] and one
requiring ["description"], keeping the existing properties (subject,
description) as-is so the validator enforces "at least one present" for the
group metadata update.
spec/controllers/api/v1/accounts/contacts/group_invite_controller_spec.rb-70-71 (1)

70-71: ⚠️ Potential issue | 🟡 Minor

Assert the error payload in revoke failure path.

Line 70 currently validates only status. Add an assertion for response.parsed_body['error'] (as done in the GET unavailable test) to protect the error contract.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@spec/controllers/api/v1/accounts/contacts/group_invite_controller_spec.rb`
around lines 70 - 71, The revoke-failure spec currently only checks HTTP status;
update the test (in
spec/controllers/api/v1/accounts/contacts/group_invite_controller_spec.rb) to
also assert the error payload by adding an assertion on
response.parsed_body['error'] (mirror the style used in the "GET unavailable"
test) — e.g. expect(response.parsed_body['error']).to be_present or to eq(...)
depending on the expected message — so the test validates the error contract as
well as the status.
app/javascript/dashboard/components/ChatList.vue-378-385 (1)

378-385: ⚠️ Potential issue | 🟡 Minor

Normalize persisted group_type before applying it.

Line 378 reads group_type from UI settings, but Line 385 accepts it without validation. This can leave the list filtered by an invalid value after stale/corrupted settings.

🔧 Suggested patch
 function setFiltersFromUISettings() {
   const { conversations_filter_by: filterBy = {} } = uiSettings.value;
   const { status, order_by: orderBy, group_type: groupType } = filterBy;
+  const validGroupTypes = ['', 'individual', 'group'];
   activeStatus.value = status || wootConstants.STATUS_TYPE.OPEN;
   activeSortBy.value = Object.values(wootConstants.SORT_BY_TYPE).includes(
     orderBy
   )
     ? orderBy
     : wootConstants.SORT_BY_TYPE.LAST_ACTIVITY_AT_DESC;
-  activeGroupType.value = groupType || '';
+  activeGroupType.value = validGroupTypes.includes(groupType) ? groupType : '';
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/javascript/dashboard/components/ChatList.vue` around lines 378 - 385,
Persisted filterBy.group_type is accepted without validation; normalize and
validate it before setting activeGroupType. In the block that reads filterBy
(use symbols filterBy, groupType, activeGroupType), coerce groupType to a string
(e.g., String(groupType).trim()) and only assign it to activeGroupType.value if
it is present in the allowed set (use
Object.values(wootConstants.GROUP_TYPE).includes(...)); otherwise set
activeGroupType.value to ''. This prevents stale/invalid persisted values from
becoming active filters.
app/javascript/dashboard/i18n/locale/en/groups.json-24-24 (1)

24-24: ⚠️ Potential issue | 🟡 Minor

Fix grammar in inbox placeholder.

"Select a inbox" should be "Select an inbox".

Suggested fix
-      "INBOX_PLACEHOLDER": "Select a inbox",
+      "INBOX_PLACEHOLDER": "Select an inbox",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/javascript/dashboard/i18n/locale/en/groups.json` at line 24, The
INBOX_PLACEHOLDER string currently reads "Select a inbox" and needs correct
grammar; update the value for the "INBOX_PLACEHOLDER" key in groups.json to
"Select an inbox" (replace the existing string literal) so the placeholder uses
"an" before "inbox".
app/services/groups/create_service.rb-18-20 (1)

18-20: ⚠️ Potential issue | 🟡 Minor

Add validation to prevent creating groups with empty participants.

The participants! parameter only ensures the value is not nil, not that it contains elements. An empty array can pass through the controller and service without validation, reaching the external WhatsApp API. Either validate that participants is non-empty in Groups::CreateService or in the controller before instantiating the service, or confirm the API gracefully handles empty participant lists.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/services/groups/create_service.rb` around lines 18 - 20, The service
currently assumes participants is non-nil but allows an empty array to reach
WhatsApp; add a presence check in Groups::CreateService (e.g., in initialize or
the perform/create method) to raise or return a validation error when
participants is empty (use participants.blank? or participants.empty?) before
calling format_participants, so the service refuses to proceed with an empty
participant list; reference the participants parameter and the
format_participants method when implementing this check.
app/javascript/dashboard/components-next/NewConversation/components/ComposeNewGroupForm.vue-86-88 (1)

86-88: ⚠️ Potential issue | 🟡 Minor

Close contacts dropdown on search failure as well.

If a prior query opened the dropdown, Line 87 clears results but leaves visibility unchanged. Explicitly closing it avoids stale empty-dropdown UI.

✅ Small fix
     } catch {
       contactResults.value = [];
+      showContactsDropdown.value = false;
     } finally {
       isSearching.value = false;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/javascript/dashboard/components-next/NewConversation/components/ComposeNewGroupForm.vue`
around lines 86 - 88, The catch block currently clears contactResults.value but
doesn't close the contacts dropdown; update the catch in ComposeNewGroupForm.vue
to also set the contacts-dropdown visibility flag to false (e.g., set
contactDropdownVisible.value = false or call setShowContactsDropdown(false) —
whatever visibility state is used in this component) so the dropdown is
explicitly closed on search failure.
swagger/paths/application/contacts/group_join_requests_handle.yml-3-8 (1)

3-8: ⚠️ Potential issue | 🟡 Minor

Use integer schema for contact_id path parameter.

Line 7 currently allows non-integer numeric values. For ID semantics, this should be an integer type.

🛠️ Suggested schema fix
   - name: contact_id
     in: path
     required: true
     schema:
-      type: number
+      type: integer
+      format: int64
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@swagger/paths/application/contacts/group_join_requests_handle.yml` around
lines 3 - 8, The path parameter "contact_id" currently uses schema type: number
which allows non-integer values; update the parameter definition for contact_id
(the path param in group_join_requests_handle) to use an integer schema (e.g.,
type: integer, optionally with format: int64) so it enforces integer ID
semantics in the OpenAPI spec.
swagger/paths/application/contacts/group_members.yml-66-66 (1)

66-66: ⚠️ Potential issue | 🟡 Minor

Participant phone format docs are inconsistent.

The text says “E.164 without +”, while E.164 canonical examples include +, and this PR’s specs use + values. Please document a single accepted format (or explicitly state both are accepted).

📝 Suggested doc adjustment
-  description: Adds new participants to the WhatsApp group. Expects an array of phone numbers (E.164 format without the + prefix).
+  description: Adds new participants to the WhatsApp group. Expects E.164 phone numbers (for example, "+5511999999999").

-              description: Phone numbers to add (e.g. ["5511999999999"])
+              description: Phone numbers to add (e.g. ["+5511999999999"])

Also applies to: 80-80

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@swagger/paths/application/contacts/group_members.yml` at line 66, The
description for the group participant add endpoint is inconsistent about phone
format; update the description string for the "Adds new participants..."
endpoint and the related parameter (the phone numbers/participants parameter) to
clearly state a single accepted E.164 format (either "E.164 with leading +, e.g.
+14155552671" or "E.164 without +, e.g. 14155552671") — choose one to match the
rest of the API spec and examples, or explicitly state both are accepted and
show both examples; ensure the wording and examples in this description and the
matching sibling description are updated to be identical and consistent with the
PR specs.
app/javascript/dashboard/components/widgets/conversation/TagGroupMembers.vue-97-99 (1)

97-99: ⚠️ Potential issue | 🟡 Minor

Guard onSelect against empty selections.

onSelect can emit undefined when the list is empty or index is out of bounds, which may break consumers expecting a member object.

✅ Suggested fix
 const onSelect = () => {
-  emit('selectAgent', selectableItems.value[selectedIndex.value]);
+  const selected = selectableItems.value[selectedIndex.value];
+  if (!selected) return;
+  emit('selectAgent', selected);
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/javascript/dashboard/components/widgets/conversation/TagGroupMembers.vue`
around lines 97 - 99, The onSelect handler can emit undefined when
selectableItems is empty or selectedIndex is out of range; update onSelect to
first check that selectableItems.value is non-empty and that selectedIndex.value
is a valid index (>=0 and < selectableItems.value.length) before calling
emit('selectAgent', ...); if the check fails, return early (or optionally emit a
clear/no-op event) so consumers never receive undefined. Ensure you reference
the existing symbols onSelect, selectableItems, selectedIndex, and the
emit('selectAgent', ...) call when making the change.
swagger/swagger.json-3616-3617 (1)

3616-3617: ⚠️ Potential issue | 🟡 Minor

Register the new Groups tag in top-level tag metadata.

The Groups tag is used in the API operations (e.g., line 3616) but is missing from the top-level tags declaration and x-tagGroups grouping. This causes weaker docs grouping and discoverability in OpenAPI UIs.

🧭 Proposed fix
   "tags": [
+    {
+      "name": "Groups",
+      "description": "Group conversation management APIs"
+    },
     {
       "name": "Accounts",
       "description": "Account management APIs"
@@
     {
       "name": "Application",
       "tags": [
         "Account AgentBots",
         "Account",
         "Agents",
         "Audit Logs",
         "Canned Responses",
         "Contacts",
         "Contact Labels",
         "Conversation Assignments",
         "Conversation Labels",
         "Conversations",
         "Custom Attributes",
         "Custom Filters",
+        "Groups",
         "Inboxes",
         "Integrations",
         "Messages",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@swagger/swagger.json` around lines 3616 - 3617, The OpenAPI document uses the
"Groups" tag in operations but it's missing from the top-level tags and tag
grouping; add a new tag entry named "Groups" to the top-level "tags" array (with
a short description) and include "Groups" inside the appropriate "x-tagGroups"
group so UIs can display it; update the top-level "tags" and "x-tagGroups"
structures to reference the "Groups" tag (ensure the exact tag string "Groups"
matches the operations).

- Fix nil safety in group_invites and group_join_requests controllers
  by replacing group_conversation.inbox.channel with @contact.group_channel
- Add before_action guard in group_members_controller to validate
  contact is a group with identifier before create/update/destroy
- Persist metadata locally in group_metadata_controller after
  provider calls (subject -> name, description -> additional_attributes)
- Add server-side allow_group_creation? check in groups_controller
- Add word boundary to mention regex to prevent matching inside words
- Remove useless catch clauses in groupMembers store (try/finally only)
- Default groupType to [] in customViewsHelper to prevent crash
- Fix swagger parameter name mismatch (contact_id -> id) across
  all group endpoint YML files for consistency
@CayoPOliveira
Copy link
Author

PR Review Feedback — All Critical Items Addressed ✅

All critical review comments from Copilot and CodeRabbit have been addressed in commit 0f6ed5b59. Summary:

# Issue Fix
1 Nil safety in group_invites_controller / group_join_requests_controller Replaced group_conversation.inbox.channel with @contact.group_channel, removed fragile group_conversation method
2 Missing guards in group_members_controller Added before_action :ensure_group_contact for create/update/destroy
3 getValuesForGroupType crash on undefined Default groupType = [] in destructuring
4 group_metadata_controller not persisting locally Added @contact.update! calls after provider calls
5 Missing allow_group_creation? server-side guard Added inbox.channel.try(:allow_group_creation?) to inbox_accessible?
6 Mention regex matches partial words (@alliance) Added \b word boundary
7 throw new Error(error) loses error context Removed useless catch clauses entirely (try/finally)
8 Swagger contact_id vs id mismatch Aligned all paths/params to use {id} consistently

Intentionally skipped:

  • page/per_page clamping — system-controlled values, not user-influenceable
  • ralph.sh path traversal / --tool guard — dev-only script, out of scope
  • baileys.d.ts Long type / Bussines typo — auto-generated reference file, not hand-maintained

@CayoPOliveira CayoPOliveira mentioned this pull request Mar 5, 2026
12 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

wontfix This will not be worked on

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants